<?php
/**
 * @package qBase
 * @subpackage model
 *
 */

/**
 * Represents an QDbStorage object
 * 
 * @package qBase
 * @subpackage model
 */
class QDbStorage extends QStorage 
{
	const DefaultPrefix = "aaa";
	/**
	 * The connection of the storage
	 *
	 * @var QSqlConnection
	 */
	private $connection;
	/**
	 * The default database of the storage, should also contain the tables with the settings
	 *
	 * @var string
	 */
	private $default_database;
	private $default_database_id;
	private $default_database_info;
	// private $settings_table;
	private $tables_prefix;
	
	private $storage_items = array();
	
	/**
	 * Info
	 *
	 * @var QDbStorageInfo
	 */
	private $info;

	public function __construct(QSqlConnection $connection = null)
	{
		$this->connection = $connection;
	}
	
	public function getInfo()
	{
		return $this->info;
	}
	
	public function getDefaultDatabase()
	{
		return $this->default_database;
	}
	
	public function getDefaultDatabaseId()
	{
		if (!$this->default_database_id)
		{
			// update DBs info
			$this->default_database_id = $this->info->getDatabaseId($this->default_database);
		}
		return $this->default_database_id;
	}

	public function getDatabaseId($db_name)
	{
		
	}

	public function loadTableInfo($table, $database = null)
	{
		
	}

	public function loadClassInfo($class)
	{
		
	}
	
	public function setStorageItem($database, $tables_pattern)
	{
		if (!isset($this->storage_items[$database]))
			$this->storage_items[$database] = array();
		else 
			if (in_array($tables_pattern, $this->storage_items[$database]))
				return false;
		$this->storage_items[$database][] = $tables_pattern;
		
		$this->setSettingsKey("StorageItems", serialize($this->storage_items));
		
		return true;
	}
	
	public function setSettingsKey($key, $value)
	{
		$this->merge($this->getSettingsTable(), array("key" => $key), array("key" => $key, "value" => $value), "id");
	}

	public function getSettingsValue($key)
	{
		$result = QSqlQuery::NewQuery()->select()->value->from()->{$this->getSettingsTable()}->where()->key->eq()->val($key)->executeQuery($this->connection);
		return $this->connection->fetchValue($result, "value");
	}
	
	/**
	 * getConnection
	 *
	 * @return QSqlConnection
	 */
	public function getConnection()
	{
		return $this->connection;
	}

	public function unsetStorageItem($database, $tables_pattern)
	{
		// var_dump("found");
		// var_dump(isset($this->storage_items[$database]));
		if (isset($this->storage_items[$database]))
		{
			$pos = array_search($tables_pattern, $this->storage_items[$database]);
			// var_dump($this->storage_items);
			if ($pos !== false)
			{
				array_splice($this->storage_items[$database], $pos, 1);
				// var_dump("spliced");
				if (count($this->storage_items[$database]) < 1)
					unset($this->storage_items[$database]);
				$this->setSettingsKey("StorageItems", serialize($this->storage_items));
				return true;
			}
			else 
				return false;
		}
		else 
			return false;
	}

	public function initStorage($default_database = null, $tables_prefix = null)
	{
		if (!$default_database)
			throw new Exception("QDBStorage::init default database not specified");
		if (!$tables_prefix)
			$tables_prefix = QDbStorage::DefaultPrefix;
		
		$this->default_database = $default_database;
		$this->tables_prefix = $tables_prefix;

		$this->storage_items = unserialize($this->getSettingsValue("StorageItems"));
		
		// var_dump($this->storage_items);
	}
	
	public function loadInfo($full = false)
	{
		if (!$this->info)
		{
			$this->info = new QDbStorageInfo($this);
			$this->info->loadInfo($full);
		}
		else 
			$this->info->loadInfo($full, true);
	}

	public function createSettings($default_database = null, $tables_prefix = "qBase", $drop_if_exists = false)
	{
		$this->tables_prefix = $tables_prefix;
		
		if ($default_database)
			$this->default_database = $default_database;
			
		$db_info = $this->connection->getDatabaseInfo($this->default_database);
		$this->default_database_info = $db_info;

		// CREATE SETTINGS TABLE
		$fields = array(
			new QSqlTableField(null, "id", QSqlTableField::TypeInt, null, null, null, null, null, true, false, true),
			new QSqlTableField(null, "id_parent", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
			new QSqlTableField(null, "key", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false),
			new QSqlTableField(null, "value", QSqlTableField::TypeVarchar, 255, null, "", "utf8", null, false, false, false),
			new QSqlTableField(null, "order", QSqlTableField::TypeInt, null, null, null, null, null, true, false, false)
			);

		$tab_settings = new QSqlTable($db_info, $tables_prefix."Settings", $fields);
		$tab_settings->create($drop_if_exists);
		$tab_settings->setCharset("utf8");

		$tab_settings->addIndex(new QSqlTableIndex($tab_settings, QSqlTableIndex::IndexUnique, "parent_key", 
			array($tab_settings->getField("id_parent"), $tab_settings->getField("key"))));

		// CREATE CODE OBJECTS TABLE
		$fields = array(
			new QSqlTableField(null, "id", QSqlTableField::TypeInt, null, null, null, null, null, true, false, true),
			new QSqlTableField(null, "class", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false),
			new QSqlTableField(null, "id_parent", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
			new QSqlTableField(null, "is_qidata", QSqlTableField::TypeBool, null, null, 0, null, null, false, false, false),
			new QSqlTableField(null, "parent_class", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false),
			new QSqlTableField(null, "comment", QSqlTableField::TypeVarchar, 255, null, "", "utf8", null, false, false, false),
			new QSqlTableField(null, "id_package", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false)
			);
		
		$php_objects = new QSqlTable($db_info, $tables_prefix."CodeObjects", $fields);
		$php_objects->create($drop_if_exists);
		$php_objects->setCharset("utf8");
		
		$php_objects->addIndex(new QSqlTableIndex($php_objects, QSqlTableIndex::IndexUnique, "class", 
			array($php_objects->getField("class"))));
		$php_objects->addIndex(new QSqlTableIndex($php_objects, QSqlTableIndex::IndexNormal, "id_parent", 
			array($php_objects->getField("id_parent"))));
		$php_objects->addIndex(new QSqlTableIndex($php_objects, QSqlTableIndex::IndexNormal, "parent_class", 
			array($php_objects->getField("parent_class"))));
		$php_objects->addIndex(new QSqlTableIndex($php_objects, QSqlTableIndex::IndexNormal, "id_package", 
			array($php_objects->getField("id_package"))));

		// CREATE CODE OBJECTS VARIABLE TABLE
		$fields = array(
			new QSqlTableField(null, "id", QSqlTableField::TypeInt, null, null, null, null, null, true, false, true),
			new QSqlTableField(null, "id_class", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
			new QSqlTableField(null, "id_type", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
			new QSqlTableField(null, "name", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false),
			new QSqlTableField(null, "inherited", QSqlTableField::TypeBool, null, null, 0, null, null, false, false, false),
			new QSqlTableField(null, "access", QSqlTableField::TypeEnum, null, "'public','private','protected'", "public", null, null, false, false, false),
			new QSqlTableField(null, "comment", QSqlTableField::TypeVarchar, 255, null, "", "utf8", null, false, false, false)
			);
		
		$php_objects_vars = new QSqlTable($db_info, $tables_prefix."CodeProperties", $fields);
		$php_objects_vars->create($drop_if_exists);
		$php_objects_vars->setCharset("utf8");
		
		$php_objects_vars->addIndex(new QSqlTableIndex($php_objects_vars, QSqlTableIndex::IndexUnique, "class_name", 
			array($php_objects_vars->getField("id_class"), $php_objects_vars->getField("name"))));
		$php_objects_vars->addIndex(new QSqlTableIndex($php_objects_vars, QSqlTableIndex::IndexNormal, "id_type", 
			array($php_objects_vars->getField("id_type"))));
		$php_objects_vars->addIndex(new QSqlTableIndex($php_objects_vars, QSqlTableIndex::IndexNormal, "access", 
			array($php_objects_vars->getField("access"))));
		
		// CREATE CODE PACKAGES TABLE
		$fields = array(
			new QSqlTableField(null, "id", QSqlTableField::TypeInt, null, null, null, null, null, true, false, true),
			new QSqlTableField(null, "id_parent", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
			new QSqlTableField(null, "name", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false)
			);
			
		$php_packages = new QSqlTable($db_info, $tables_prefix."CodePackages", $fields);
		$php_packages->create($drop_if_exists);
		$php_packages->setCharset("utf8");

		$php_packages->addIndex(new QSqlTableIndex($php_packages, QSqlTableIndex::IndexUnique, "parent_name", 
			array($php_packages->getField("id_parent"), $php_packages->getField("name"))));

		// CREATE CODE ACCESS TYPES TABLE
		
		/*
		$fields = array(
			new QSqlTableField(null, "id", QSqlTableField::TypeInt, null, null, null, null, null, true, false, true),
			new QSqlTableField(null, "name", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false)
			);
			
		$php_accesstypes = new QSqlTable($db_info, $tables_prefix."CodeAccessTypes", $fields);
		$php_accesstypes->setCharset("utf8");
		$php_accesstypes->create($drop_if_exists);
		
		$php_accesstypes->addIndex(new QSqlTableIndex($php_accesstypes, QSqlTableIndex::IndexUnique, "name", 
			array($php_accesstypes->getField("name"))));
		*/
		
		// CREATE STORE DATABASES TABLE
		$fields = array(
			new QSqlTableField(null, "id", QSqlTableField::TypeInt, null, null, null, null, null, true, false, true),
			new QSqlTableField(null, "name", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false),
			new QSqlTableField(null, "charset", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false),
			new QSqlTableField(null, "collation", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false)
			);

		$dbs = new QSqlTable($db_info, $tables_prefix."Databases", $fields);
		$dbs->create($drop_if_exists);
		$dbs->setCharset("utf8");

		$dbs->addIndex(new QSqlTableIndex($dbs, QSqlTableIndex::IndexUnique, "name", 
			array($dbs->getField("name"))));

		// CREATE STORE TABLES TABLE
		$fields = array(
			new QSqlTableField(null, "id", QSqlTableField::TypeInt, null, null, null, null, null, true, false, true),
			new QSqlTableField(null, "id_database", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
			new QSqlTableField(null, "name", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false),
			new QSqlTableField(null, "charset", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false),
			new QSqlTableField(null, "collation", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false),
			new QSqlTableField(null, "engine", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false)
			);

		$tb_tables = new QSqlTable($db_info, $tables_prefix."Tables", $fields);
		$tb_tables->create($drop_if_exists);
		$tb_tables->setCharset("utf8");

		$tb_tables->addIndex(new QSqlTableIndex($tb_tables, QSqlTableIndex::IndexUnique, "database_name", 
			array($tb_tables->getField("id_database"), $tb_tables->getField("name"))));

		// CREATE STORE TABLE FIELDS TABLE
		$fields = array(
			new QSqlTableField(null, "id", QSqlTableField::TypeInt, null, null, null, null, null, true, false, true),
			new QSqlTableField(null, "id_table", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
			new QSqlTableField(null, "name", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false),
			new QSqlTableField(null, "type", QSqlTableField::TypeInt, null, null, null, null, null, true, false, false),
			new QSqlTableField(null, "length", QSqlTableField::TypeInt, null, null, null, null, null, true, false, false),
			
			new QSqlTableField(null, "charset", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false),
			new QSqlTableField(null, "collation", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false),
			
			new QSqlTableField(null, "unsigned", QSqlTableField::TypeBool, null, null, null, null, null, false, false, false),
			new QSqlTableField(null, "null", QSqlTableField::TypeBool, null, null, null, null, null, false, false, false),
			new QSqlTableField(null, "auto_increment", QSqlTableField::TypeBool, null, null, null, null, null, false, false, false),

			new QSqlTableField(null, "values", QSqlTableField::TypeText, null, null, "", "utf8", null, false, false, false),
			new QSqlTableField(null, "default", QSqlTableField::TypeText, null, null, "", "utf8", null, false, true, false)
			);

		$tb_fields = new QSqlTable($db_info, $tables_prefix."Fields", $fields);
		$tb_fields->create($drop_if_exists);
		$tb_fields->setCharset("utf8");

		$tb_fields->addIndex(new QSqlTableIndex($tb_fields, QSqlTableIndex::IndexUnique, "table_name", 
			array($tb_fields->getField("id_table"), $tb_fields->getField("name"))));

		// CREATE INDEX TABLE
		$fields = array(
			new QSqlTableField(null, "id", QSqlTableField::TypeInt, null, null, null, null, null, true, false, true),
			new QSqlTableField(null, "id_table", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
			new QSqlTableField(null, "name", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false),
			new QSqlTableField(null, "type", QSqlTableField::TypeInt, null, null, null, null, null, true, false, false),
			new QSqlTableField(null, "id_foreign_key_to", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false)
			);
		$tb_indexes = new QSqlTable($db_info, $tables_prefix."Indexes", $fields);
		$tb_indexes->create($drop_if_exists);
		$tb_indexes->setCharset("utf8");
		
		$tb_indexes->addIndex(new QSqlTableIndex($tb_indexes, QSqlTableIndex::IndexUnique, "table_name", 
			array($tb_indexes->getField("id_table"), $tb_indexes->getField("name"))));
			
		$tb_indexes->addIndex(new QSqlTableIndex($tb_indexes, QSqlTableIndex::IndexNormal, "type_indx", 
			array($tb_indexes->getField("type"))));
			
		$tb_indexes->addIndex(new QSqlTableIndex($tb_indexes, QSqlTableIndex::IndexNormal, "id_foreign_key_to", 
			array($tb_indexes->getField("id_foreign_key_to"))));
			
		// CREATE INDEX TABLE FIELDS
		$fields = array(
			new QSqlTableField(null, "id", QSqlTableField::TypeInt, null, null, null, null, null, true, false, true),
			new QSqlTableField(null, "id_index", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
			new QSqlTableField(null, "id_field", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
			new QSqlTableField(null, "order", QSqlTableField::TypeInt, null, null, "0", null, null, true, false, false)
			);
		$tb_indexes_fields = new QSqlTable($db_info, $tables_prefix."IndexesFields", $fields);
		$tb_indexes_fields->create($drop_if_exists);
		$tb_indexes_fields->setCharset("utf8");

		$tb_indexes_fields->addIndex(new QSqlTableIndex($tb_indexes_fields, QSqlTableIndex::IndexUnique, "index_field", 
			array($tb_indexes_fields->getField("id_index"), $tb_indexes_fields->getField("id_field"))));

		// CREATE MAP OBJECTS TABLE
		
		$fields = array(
			new QSqlTableField(null, "id", QSqlTableField::TypeInt, null, null, null, null, null, true, false, true),
			new QSqlTableField(null, "id_object", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
			new QSqlTableField(null, "id_table", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
			new QSqlTableField(null, "name", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false)
			);
		$tb_map_objects = new QSqlTable($db_info, $tables_prefix."MapObjects", $fields);
		$tb_map_objects->create($drop_if_exists);
		$tb_map_objects->setCharset("utf8");

		$tb_map_objects->addIndex(new QSqlTableIndex($tb_map_objects, QSqlTableIndex::IndexNormal, "id_object", 
			array($tb_map_objects->getField("id_object"))));

		$tb_map_objects->addIndex(new QSqlTableIndex($tb_map_objects, QSqlTableIndex::IndexNormal, "id_table", 
			array($tb_map_objects->getField("id_table"))));
			
		$tb_map_objects->addIndex(new QSqlTableIndex($tb_map_objects, QSqlTableIndex::IndexUnique, "name", 
			array($tb_map_objects->getField("name"))));

		// CREATE MAP OBJECTS FILTERS TABLE

		$fields = array(
			new QSqlTableField(null, "id", QSqlTableField::TypeInt, null, null, null, null, null, true, false, true),
			new QSqlTableField(null, "id_map_object", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
			new QSqlTableField(null, "id_field", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
			new QSqlTableField(null, "expression", QSqlTableField::TypeText, null, null, "", "utf8", null, false, false, false)
			);
			
		$tb_map_objects_filters = new QSqlTable($db_info, $tables_prefix."MapObjectsFilters", $fields);
		$tb_map_objects_filters->create($drop_if_exists);
		$tb_map_objects_filters->setCharset("utf8");

		$tb_map_objects_filters->addIndex(new QSqlTableIndex($tb_map_objects_filters, QSqlTableIndex::IndexUnique, "object_field", 
			array($tb_map_objects_filters->getField("id_map_object"), $tb_map_objects_filters->getField("id_field"))));

		// CREATE MAP PROPERTIES TABLE

		$fields = array(
				new QSqlTableField(null, "id", QSqlTableField::TypeInt, null, null, null, null, null, true, false, true),
				new QSqlTableField(null, "id_map_object", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
				new QSqlTableField(null, "id_property", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
				new QSqlTableField(null, "id_field", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
				new QSqlTableField(null, "id_type", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
				new QSqlTableField(null, "name", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false),
				new QSqlTableField(null, "load_by_default", QSqlTableField::TypeBool, null, null, 0, null, null, false, false, false),
				new QSqlTableField(null, "id_fk_index", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
				new QSqlTableField(null, "is_collection", QSqlTableField::TypeBool, null, null, 0, null, null, false, false, false),
				new QSqlTableField(null, "id_collection_table", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
				new QSqlTableField(null, "id_collection_fk_owner", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
				new QSqlTableField(null, "id_collection_fk_item", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false)
			);
		$tb_map_properties = new QSqlTable($db_info, $tables_prefix."MapProperties", $fields);
		$tb_map_properties->create($drop_if_exists);
		$tb_map_properties->setCharset("utf8");

		$tb_map_properties->addIndex(new QSqlTableIndex($tb_map_properties, QSqlTableIndex::IndexUnique, "map_object_name", 
			array($tb_map_properties->getField("id_map_object"), $tb_map_properties->getField("name"))));
		$tb_map_properties->addIndex(new QSqlTableIndex($tb_map_properties, QSqlTableIndex::IndexNormal, "id_property", 
			array($tb_map_properties->getField("id_property"))));
		$tb_map_properties->addIndex(new QSqlTableIndex($tb_map_properties, QSqlTableIndex::IndexNormal, "id_field", 
			array($tb_map_properties->getField("id_field"))));
		$tb_map_properties->addIndex(new QSqlTableIndex($tb_map_properties, QSqlTableIndex::IndexNormal, "id_type", 
			array($tb_map_properties->getField("id_type"))));
		$tb_map_properties->addIndex(new QSqlTableIndex($tb_map_properties, QSqlTableIndex::IndexNormal, "id_fk_index", 
			array($tb_map_properties->getField("id_fk_index"))));
		$tb_map_properties->addIndex(new QSqlTableIndex($tb_map_properties, QSqlTableIndex::IndexNormal, "id_collection_table", 
			array($tb_map_properties->getField("id_collection_table"))));
		$tb_map_properties->addIndex(new QSqlTableIndex($tb_map_properties, QSqlTableIndex::IndexNormal, "id_collection_fk_owner", 
			array($tb_map_properties->getField("id_collection_fk_owner"))));
		$tb_map_properties->addIndex(new QSqlTableIndex($tb_map_properties, QSqlTableIndex::IndexNormal, "id_collection_fk_item", 
			array($tb_map_properties->getField("id_collection_fk_item"))));

		// CREATE MAP COLLECTIONS FILTERS TABLE

		$fields = array(
				new QSqlTableField(null, "id", QSqlTableField::TypeInt, null, null, null, null, null, true, false, true),
				new QSqlTableField(null, "id_property", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
				new QSqlTableField(null, "id_field", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
				new QSqlTableField(null, "expression", QSqlTableField::TypeText, null, null, "", "utf8", null, false, false, false)
			);
		$tb_map_collections_filters = new QSqlTable($db_info, $tables_prefix."MapCollectionsFilters", $fields);
		$tb_map_collections_filters->create($drop_if_exists);
		$tb_map_collections_filters->setCharset("utf8");

		$tb_map_collections_filters->addIndex(new QSqlTableIndex($tb_map_collections_filters, QSqlTableIndex::IndexUnique, "property_field", 
			array($tb_map_collections_filters->getField("id_property"), $tb_map_collections_filters->getField("id_field"))));
			
		// CREATE MAP INDEXES
		$fields = array(
			new QSqlTableField(null, "id", QSqlTableField::TypeInt, null, null, null, null, null, true, false, true),
			new QSqlTableField(null, "id_table", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
			new QSqlTableField(null, "name", QSqlTableField::TypeVarchar, 64, null, "", "utf8", null, false, false, false),
			new QSqlTableField(null, "type", QSqlTableField::TypeInt, null, null, null, null, null, true, false, false),
			new QSqlTableField(null, "id_foreign_key_to", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false)
			);
		$tb_indexes = new QSqlTable($db_info, $tables_prefix."MapIndexes", $fields);
		$tb_indexes->create($drop_if_exists);
		$tb_indexes->setCharset("utf8");
		
		$tb_indexes->addIndex(new QSqlTableIndex($tb_indexes, QSqlTableIndex::IndexUnique, "table_name", 
			array($tb_indexes->getField("id_table"), $tb_indexes->getField("name"))));
			
		$tb_indexes->addIndex(new QSqlTableIndex($tb_indexes, QSqlTableIndex::IndexNormal, "type_indx", 
			array($tb_indexes->getField("type"))));
			
		$tb_indexes->addIndex(new QSqlTableIndex($tb_indexes, QSqlTableIndex::IndexNormal, "id_foreign_key_to", 
			array($tb_indexes->getField("id_foreign_key_to"))));
			
		// CREATE MAP INDEX TABLE FIELDS
		$fields = array(
			new QSqlTableField(null, "id", QSqlTableField::TypeInt, null, null, null, null, null, true, false, true),
			new QSqlTableField(null, "id_index", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
			new QSqlTableField(null, "id_field", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
			new QSqlTableField(null, "order", QSqlTableField::TypeInt, null, null, "0", null, null, true, false, false)
			);
		$tb_indexes_fields = new QSqlTable($db_info, $tables_prefix."MapIndexesFields", $fields);
		$tb_indexes_fields->create($drop_if_exists);
		$tb_indexes_fields->setCharset("utf8");

		$tb_indexes_fields->addIndex(new QSqlTableIndex($tb_indexes_fields, QSqlTableIndex::IndexUnique, "index_field", 
			array($tb_indexes_fields->getField("id_index"), $tb_indexes_fields->getField("id_field"))));

		// CREATE MAP FOREIGN KEYS FILTERS TABLE

		$fields = array(
				new QSqlTableField(null, "id", QSqlTableField::TypeInt, null, null, null, null, null, true, false, true),
				new QSqlTableField(null, "id_fk_index", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
				new QSqlTableField(null, "id_field", QSqlTableField::TypeInt, null, null, 0, null, null, true, false, false),
				new QSqlTableField(null, "expression", QSqlTableField::TypeText, null, null, "", "utf8", null, false, false, false)
			);
		$tb_map_fk_filters = new QSqlTable($db_info, $tables_prefix."MapForeignKeysFilters", $fields);
		$tb_map_fk_filters->create($drop_if_exists);
		$tb_map_fk_filters->setCharset("utf8");

		$tb_map_fk_filters->addIndex(new QSqlTableIndex($tb_map_fk_filters, QSqlTableIndex::IndexUnique, "fk_index_field", 
			array($tb_map_fk_filters->getField("id_fk_index"), $tb_map_fk_filters->getField("id_field"))));

		// =======================

		$this->connection->selectDb($db_info->getName());

		$q = new QSqlQuery();
		$q->insert()->{$this->getSettingsTable()}->lb()->key->value->rb()->values()->lb()->val("", "key")->val("", "value")->rb();

		$q->setKeys(array("key" => "DefaultDatabase", "value" => $db_info->getName()))->executeQuery($this->connection);
		// $q->setKeys(array("key" => "SettingsTable", "value" => $tables_prefix."Settings"))->executeQuery($this->connection);
		$q->setKeys(array("key" => "TablesPrefix", "value" => $tables_prefix))->executeQuery($this->connection);

		/*
		$q->setKeys(array("key" => "Table_CodeObjects", "value" => $tables_prefix."CodeObjects"))->executeQuery($this->connection);
		$q->setKeys(array("key" => "Table_CodeProperties", "value" => $tables_prefix."CodeProperties"))->executeQuery($this->connection);
		$q->setKeys(array("key" => "Table_CodeAccessTypes", "value" => $tables_prefix."CodeAccessTypes"))->executeQuery($this->connection);
		$q->setKeys(array("key" => "Table_CodePackages", "value" => $tables_prefix."CodePackages"))->executeQuery($this->connection);

		$q->setKeys(array("key" => "Table_Databases", "value" => $tables_prefix."Databases"))->executeQuery($this->connection);
		$q->setKeys(array("key" => "Table_Fields", "value" => $tables_prefix."Fields"))->executeQuery($this->connection);
		$q->setKeys(array("key" => "Table_Tables", "value" => $tables_prefix."Tables"))->executeQuery($this->connection);
		*/

		/*
		$q = new QSqlQuery();
		$q->insert()->{$tables_prefix."CodeAccessTypes"}->lb()->name->rb()->values()->lb()->val("", "name")->rb();
		
		$q->setKeys(array("name" => "public"))->executeQuery($this->connection);
		$q->setKeys(array("name" => "private"))->executeQuery($this->connection);
		$q->setKeys(array("name" => "protected"))->executeQuery($this->connection);
		*/
	}
	
	public function updateStorageItemsInfo()
	{
		if ($this->storage_items)
		{
			foreach ($this->storage_items as $database => $tables_patterns)
			{
				$this->updateDatabaseInfo($database, $tables_patterns);
			}
		}
	}
	
	public function updateDatabaseInfo($database, $tables_pattern = "*")
	{
		$db_info = $this->connection->getDatabaseInfo($database);
		
		$this->connection->selectDb($this->default_database);
		$id_database = $this->merge($this->getFrameTable("Databases"), array("name" => $database), 
			array("name" => $database, "charset" => $db_info->getCharset(), "collation" => $db_info->getCollation() ), "id");

		foreach ($tables_pattern as $tables_patt)
		{
			if ($tables_patt == "*")
			{
				// get all tables and update
				$tables = $this->connection->getTablesInDatabase($database);
				// var_dump($tables);
				foreach ($tables as $tab)
				{
					if (($database == $this->default_database) && (substr($tab, 0, strlen($this->tables_prefix)) == $this->tables_prefix))
					{
						// nothing
					}
					else 
						$this->updateTableInfo($database, $tab);
				}
			}
			else 
			{
				// one table
				$this->updateTableInfo($database, $tables_patt);
			}
		}
	}
	
	public function updateTableInfo($database, $table, $id_database = null)
	{
		$table_info = $this->connection->getTableInfo($database, $table);

		$this->selectDefaultDatabase();
		if (is_null($id_database))
		{
			$result = QSqlQuery::NewQuery()->select()->id->from()->{$this->getFrameTable("Databases")}->
						where()->name->eq()->val($database)->executeQuery($this->connection);
			$id_database = $this->connection->fetchValue($result, "id");
			
			if (!$id_database)
				throw new Exception("The table has no database defined");
		}

		
		/* id_database, name, charset, collation, engine*/
		$id_table = $this->merge($this->getFrameTable("Tables"), array("id_database" => $id_database, "name" => $table),
			array("id_database" => $id_database, "name" => $table, "charset" => $table_info->getCharset(), 
				"collation" => $table_info->getCollation(), "engine" => $table_info->getEngine()),   "id");

		/*
		$pk_fields = array();
		
		foreach ($table_info->getIndexes() as $index)
		{
			if ($index->getType() == QSqlTableIndex::IndexPrimary)
			{
				foreach ($index->getFields() as $index_field)
					$pk_fields[] = $index_field->getName();
				// no more than one PK
				break;
			}
			
		}
		*/
		// var_dump($id_table);
		$fields_ids = array();

		foreach ($table_info->getFields() as $field)
		{
			/* id_table 	name 	type 	length 	charset 	collation 	unsigned 	null 	auto_increment 	values 	default */
			$field_id = $this->merge($this->getFrameTable("Fields"), array("id_table" => $id_table, "name" => $field->getName()),
				array("id_table" => $id_table , "name" => $field->getName(),
					"type" => $field->getType() , "length" => $field->getLength(), 
					"charset" => $field->getCharset() , "collation" => $field->getCollation(), 
					"unsigned" => $field->getUnsigned() , "null" => $field->getNull(), 
					"auto_increment" => $field->getAutoIncrement() , "values" => $field->getValues(), 
					"default" => $field->getDefault()
				), "id");
				
			$fields_ids[$field->getName()] = $field_id;
		}
		
		foreach ($table_info->getIndexes() as $index)
		{
			$index_id = $this->merge($this->getFrameTable("Indexes"), 
							array("id_table" => $id_table, "name" => $index->getName()),
							array("id_table" => $id_table, "name" => $index->getName(), "type" => $index->getType()),
							"id");
			
			$order = 1;
			foreach ($index->getFields() as $index_field)
			{
				$this->merge($this->getFrameTable("IndexesFields"), 
						array("id_index" => $index_id, "id_field" => $fields_ids[$index_field->getName()]),
						array("id_index" => $index_id, "id_field" => $fields_ids[$index_field->getName()], "order" => $order),
						"id");
				$order++;
			}
		}
	}

	public function updateClassesInfo()
	{
		if (!$this->default_database_info)
			$this->default_database_info = $this->connection->getDatabaseInfo($this->default_database);

		QAutoload::LoadAllClasses();
		$classes = get_declared_classes();

		$updated_classes = array();
		foreach ($classes as $class)
		{
			$this->updateClassInfo($class, $updated_classes);
		}

		/*$q = QSqlQuery::NewQuery()->update()->{$this->getFrameTable("CodeObjects")}->
			set()->id_parent->eq()->lb()->
				select()->id->from()->{$this->getFrameTable("CodeObjects")}->as()->->
					where()->parent_name*/
	}

	public function getSettingsTable()
	{
		return $this->getFrameTable("Settings");
	}
	
	public function selectDefaultDatabase()
	{
		$this->connection->selectDb($this->default_database);
	}
	
	/**
	 * This function will update the class info of the specified class
	 * Parent classes will also be updated
	 *
	 * @param string $class
	 * @param array $updated_classes The list of the classes that have been updated in the process
	 * @return boolean
	 */
	public function updateClassInfo($class, &$updated_classes)
	{
		if (isset($updated_classes[$class]) && $updated_classes[$class])
			return true;
		
		QAutoload::AutoloadClass($class);
		$class_info = new ReflectionClass($class);
		
		$interfaces = $class_info->getInterfaceNames();
		if (!in_array("QIData", $interfaces))
			return false;
			
		// check the parent
		$parent_class = get_parent_class($class);
		$parent_class_info = new ReflectionClass($parent_class);		
		$parent_interfaces = $parent_class_info->getInterfaceNames();
		if (in_array("QIData", $interfaces))
		{
			$this->updateClassInfo($parent_class, $updated_classes);
		}
		
		// var_dump($this->default_database_info);
		$this->connection->selectDb($this->default_database_info->getName());
		
		$id_parent_result = QSqlQuery::NewQuery()->select()->id->from()->{$this->getFrameTable("CodeObjects")}->
						where()->class->eq()->val($parent_class)->executeQuery($this->connection);
		$id_parent = $this->connection->fetchValue($id_parent_result, "id");
		if (!$id_parent) $id_parent = 0;

		$update_data = array("class" => $class, "id_parent" => $id_parent, "is_qidata" => "1",
		 					"parent_class" => $parent_class, "comment" => $class_info->getDocComment());
		$class_id = $this->merge($this->getFrameTable("CodeObjects"), array("class" => $class), $update_data, "id");

		// var_dump($class_id);
		// UPDATE PROPERTIES
		
		foreach ($class_info->getProperties() as $prop)
		{
			if ($prop->isStatic())
				continue;
			
			$prop_name = $prop->getName();
      		if (($prop_name == "_class_name") || ($prop_name == "_storage") || ($prop_name == "_storage_id"))
      			continue;

     		// getDocComment
      		/*
			 	id_class, id_type, name, access, comment, inherited
			 */
      		$prop_comment = $prop->getDocComment();
      		$prop_type = $this->getTypeFromComment($prop_comment);
      		
      		if (!$prop_type)
      			throw new Exception("Could not extract the variable type for `{$prop_name}` in class `{$class}`");
      		
      		$prop_id_type = $this->merge($this->getFrameTable("CodeObjects"), array("class" => $prop_type),
      			array("class" => $prop_type), "id");

			if ($prop->isPublic())
				$prop_access = "public";
			else if ($prop->isPrivate())
				$prop_access = "private";
			else if ($prop->isProtected())
				$prop_access = "protected";
			else 
				$prop_access = "public";
				
			$prop_inherited = "0";
			
			if ($id_parent > 0)
			{
				$result = QSqlQuery::NewQuery()->select()->id->from()->{$this->getFrameTable("CodeProperties")}->where()->
					id_class->eq()->val($id_parent)->and()->name->eq()->val($prop_name)->executeQuery($this->connection);
				if ($this->connection->countRows($result) > 0)
					$prop_inherited = "1";
			}
			
      		$this->merge($this->getFrameTable("CodeProperties"), array("id_class" => $class_id, "name" => $prop_name), 
      			array("id_class" => $class_id, "name" => $prop_name, "id_type" => $prop_id_type, "inherited" => $prop_inherited,
      						"comment" => $prop_comment, "access" => $prop_access), "id");
		}
		
		// var_dump($result);
		// qBaseCodeObjects
		// qBaseCodeProperties
		// qBaseCodePackages
		$updated_classes[$class] = true;
		return true;
	}
	
	public function getTypeFromComment($comment)
	{
		/**
    * The name of the hmmm ...
    *
    * @var string
    */
		$results = array();
		if ( preg_match("#(\s|\*)*@var\s+?(.*?)(\s|$)#", $comment, $results) )
		{
			return $results[2];
		}
		else 
			return null;
	}
	
	public function merge($table, $conditions, $columns_and_values, $id_column = null)
	{
		$q = QSqlQuery::NewQuery()->select();
		$has_id = false;
		foreach ($conditions as $c => $val)
		{
			if ($id_column && ($c == $id_column))
				$has_id = true;
			$q->{$c};
		}
		if ($id_column && (!$has_id))
			$q->{$id_column};
		$q->from()->{$table}->where();
		
		$pos = 0;
		foreach ($conditions as $c => $val)
		{
			if ($pos > 0)
				$q->and();
			$q->{$c}->eq()->val($val);
			$pos++;
		}

		$result = $q->executeQuery($this->connection);

		if ($this->connection->countRows($result) == 0)
		{
			QSqlQuery::GetInsertQuery($table, $columns_and_values)->executeQuery($this->connection);
			$id = $this->connection->getInsertId();
		}
		else 
		{
			if ($id_column)
				$id = $this->connection->fetchValue($result, $id_column);
			else 
				$id = null;
			// $conditions = array();
			QSqlQuery::GetUpdateQuery($table, $columns_and_values, $conditions)->executeQuery($this->connection);
		}
		
		return $id;
	}

	public function getFrameTable($name)
	{
		return $this->tables_prefix.$name;
	}

	public function uploadDatabaseInfo($database, $tables = "*", $avoid_tables = null)
	{
		// 
	}
	
	
	public function queryValue($table, $id_column, $id, $field)
	{
		$query = QDbStorage::GetOneFieldQuery($table, $id_column, $id, $field);
		return $this->connection->queryOneValue($query, $field);
	}
	
		
	/**
	 * Gets the query needed to extract one field on a primary key condition
	 *
	 * @param string $table
	 * @param string $id_column
	 * @param string $id
	 * @param string $field
	 * @return QSqlQuery
	 */
	public static function GetOneFieldQuery($table, $id_column, $id, $field)
	{
		return QSqlQuery::NewQuery()->select()->{$field}->from()->{$table}->where()->{$id_column}->eq->val($id);
	}

	
	/**
	 * Saves an object as a new item in the current QStorage object
	 *
	 * @param QIData $item
	 * @param mixed $container QStorageContainer or the string representation of the storage container
	 * @return QStorageId The storage id of the new object
	 */
	public function create(QIData $item, $container = null)
	{
		
	}

	/**
	 * Loads data from the storage to the specified data object based on the id
	 *
	 * @param QStorageId $id
	 * @param mixed $container QStorageContainer or the string representation of the storage container
	 * @param array $properties_to_load The properties to load or null to use defaults
	 * @param QIData $item
	 * 
	 * @return QIData
	 */
	public function load(QStorageId $id, $container = null, $properties_to_load = null, QIData $item = null)
	{
		
	}

	/**
	 * Loads data from the storage to the specified data object based on a query
	 *
	 * @param QSqlQuery $query
	 * @param mixed $container QStorageContainer or the string representation of the storage container
	 * @param array $properties_to_load
	 * @param QIData $item
	 * 
	 * @return QIData
	 */
	public function loadOneFromQuery(QSqlQuery $query, $container = null, $properties_to_load = null, QIData $item = null)
	{
		
	}

	/**
	 * Loads data from the storage to the specified data object based on a query
	 *
	 * @param QSqlQuery $query
	 * @param mixed $container QStorageContainer or the string representation of the storage container
	 * @param array $properties_to_load
	 * 
	 * @return QDataArray
	 */
	public function loadFromQuery(QSqlQuery $query, $container = null, $properties_to_load = null)
	{
		
	}

	/**
	 * Saves the object to the storage
	 *
	 * @param QIData $item
	 * @param array $properties_to_save Array of string
	 */
	public function save(QIData $item, $properties_to_save = null)
	{
		
	}
	
	/**
	 * Deletes the object from the storage. If the object existed returns true
	 *
	 * @param QIData $item
	 * @return boolean
	 */
	public function delete(QIData $item)
	{
		
	}
	
	/**
	 * Loads the specified properties for the specified object from the storage
	 *
	 * @param QIData $item
	 * @param array $properties_names
	 * @return array
	 */
	public function loadProperties(QIData $item, $properties_names)
	{
		
	}

	/**
	 * Gets the default container for the specified class
	 *
	 * @param string $class_name
	 * @return QStorageContainer
	 */
	public function getDefaultContainerForClass($class_name)
	{
		
	}
	/**
	 * Gets all storage containers 
	 *
	 * @return array
	 */
	public function getAllContainers()
	{
		
	}
}

?>